package gui.panels.configurations;

import gui.actions.ConfigurationAction;
import gui.actions.QueryCharactersAction;
import gui.components.LimitedAsciiDocument;
import gui.components.StackComboBox;
import gui.interfaces.ValidatorInterface;
import gui.models.ConfigurationModel;
import gui.utils.ComponentUtils;

import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import common.enums.DateOfBirthEnum;
import common.enums.HeightEnum;
import common.enums.WeightEnum;
import common.types.FictionalCharacter;
import common.types.Pair;
import common.utils.CommonUtils;

public class EntryPanel extends JPanel implements Observer, ActionListener {

	private static final long serialVersionUID = 1L;
	private static final String EMPTY_STRING = "";
 
    private JButton applyButton;  
    private JTextField searchTextField;
    private JButton searchButton;
    private JComboBox searchResultsComboBox;
    
    private EntryMode mode;
    private Map<CharacterPropertyEnum,JComponent> propertiesComponents;
    private final ConfigurationModel model;
	private JLabel foundEntriesLabel;

    public EntryPanel(EntryMode mode) {
        this.mode = mode;
		this.model = new ConfigurationModel();
		model.addObserver(this);
        buildPanel();
        initMode();
    }

	public void buildPanel() {
        
        // create components
        searchButton = ComponentUtils.createButton("Search");
        searchButton.addActionListener(new QueryCharactersAction(this, model));
        searchButton.setEnabled(false);    
        
        searchTextField = new JTextField();
        searchTextField.setDocument(new LimitedAsciiDocument(63));
        ComponentUtils.installValidator(searchTextField, new ValidatorInterface() {
			
			@Override
			public void validate(String value) {	
				searchButton.setEnabled(!CommonUtils.isEmpty(value));
				model.setSearchName(value);		
			}
		});
        
        DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(model.getCharacters().toArray());
        searchResultsComboBox = new JComboBox(comboBoxModel);
        searchResultsComboBox.addActionListener(new ActionListener() {	
        	
			@Override
			public void actionPerformed(ActionEvent e) {
				model.setSelectedCharacter((FictionalCharacter) searchResultsComboBox.getSelectedItem());
				displayCharacter(model.getSelectedCharacter());		
			}
		});
        
        foundEntriesLabel = new JLabel("Search status: no search results");
        
        applyButton = ComponentUtils.createButton(mode.getApplyButtonText());
        ConfigurationAction configurationAction = new ConfigurationAction(this, model, mode);
        applyButton.addActionListener(ComponentUtils.createDoubleAction(this, configurationAction));
        
        // forms
        CellConstraints cc = new CellConstraints();

        FormLayout mainLayout = new FormLayout("440px", "p,3dlu,p,3dlu,p,3dlu,p,p:g,p");
//        JPanel mainPanel = new FormDebugPanel();
        JPanel mainPanel = new JPanel();
        PanelBuilder mainPanelBuilder = new PanelBuilder(mainLayout, mainPanel);
        mainPanelBuilder.setDefaultDialogBorder();
        
        FormLayout searchLayout = new FormLayout("p:g,3dlu,p", "p,3dlu,p,3dlu,p");
//        JPanel searchPanel = new FormDebugPanel();
        JPanel searchPanel = new JPanel();
        searchPanel.setOpaque(false);
        PanelBuilder searchPanelBuilder = new PanelBuilder(searchLayout, searchPanel);
        
        String rowConstaints = "p";
        for (int i = 0; i < CharacterPropertyEnum.values().length; i+=2) {
        	rowConstaints += ",3dlu,p";
		}
        
        FormLayout fieldsLayout = new FormLayout("p,3dlu,95dlu,p:g,p,3dlu,95dlu", rowConstaints);
//        JPanel fieldsPanel = new FormDebugPanel();
        JPanel fieldsPanel = new JPanel();
        fieldsPanel.setOpaque(false);
        PanelBuilder fieldsPanelBuilder = new PanelBuilder(fieldsLayout, fieldsPanel);
        
        mainPanelBuilder.addSeparator("Search Entry", cc.xy(1, 1));
        
        searchPanelBuilder.add(searchTextField, cc.xy(1,1));
        searchPanelBuilder.add(searchButton, cc.xy(3, 1, "right, fill"));
        searchPanelBuilder.add(searchResultsComboBox, cc.xyw(1, 3, 3, "fill, fill"));
        searchPanelBuilder.add(foundEntriesLabel, cc.xyw(1, 5, 3, "fill, fill"));
        
        mainPanelBuilder.add(searchPanel, cc.xy(1,3));
        mainPanelBuilder.addSeparator("Information", cc.xy(1, 5));
        
        propertiesComponents = new HashMap<CharacterPropertyEnum, JComponent>();
        for (int i = 0, row = 1; i < CharacterPropertyEnum.values().length; i++, row += 2) {
			if (CharacterPropertyEnum.values()[i].isLong()) {
				JComponent component = createProperComponent(CharacterPropertyEnum.values()[i]);
				propertiesComponents.put(CharacterPropertyEnum.values()[i], component);
				fieldsPanelBuilder.addLabel(CharacterPropertyEnum.values()[i].getPropertyName() + ":", cc.xy(1, row));
				fieldsPanelBuilder.add(component, cc.xyw(3, row, 5, "fill, fill"));
			} else {
				for (int j = 0; j < 2; j++) {
					JComponent component = createProperComponent(CharacterPropertyEnum.values()[i+j]);
					propertiesComponents.put(CharacterPropertyEnum.values()[i+j], component);
			        fieldsPanelBuilder.addLabel(CharacterPropertyEnum.values()[i+j].getPropertyName() + ":", cc.xy(1 + (j*4), row));
			        fieldsPanelBuilder.add(component, cc.xy(3 + (j*4), row));
				}
				i++;
			}
		}
                
        mainPanelBuilder.add(fieldsPanel, cc.xy(1,7));
        mainPanelBuilder.add(applyButton, cc.xy(1, 9, "center, fill"));
            
        mainPanel.setOpaque(false);

        setLayout(new CardLayout());
        setOpaque(false);
        add("form", mainPanel);         
    }
	
    private void initMode() {
		switch (mode) {
		case New:
			model.createNewCharacter();
			displayCharacter(model.getSelectedCharacter());
			JTextField nameField = getPropertyTextField(CharacterPropertyEnum.NAME);
			ComponentUtils.installValidator(nameField, new ValidatorInterface() {		
				@Override
				public void validate(String value) {
					applyButton.setEnabled(value != null && !value.isEmpty());	
				}
			});
			applyButton.setEnabled(false);
			setDateOfBirthConnection();
			break;
		case Edit:
			getPropertyTextField(CharacterPropertyEnum.NAME).setEditable(false);
			setDateOfBirthConnection();
		case Delete:
			setAllEnabled(false);
		}	
	}
    
    private void setDateOfBirthConnection() {
        final JTextField field = getPropertyTextField(CharacterPropertyEnum.OTHER_DATE);
        final JComboBox combo = getPropertyComboBox(CharacterPropertyEnum.DATE_OF_BIRTH);    
        combo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (e.getActionCommand().equals("comboBoxChanged")) {
                    DateOfBirthEnum dateOfBirth = (DateOfBirthEnum)combo.getSelectedItem();
                    field.setEditable(dateOfBirth == DateOfBirthEnum.NULL);
                }
            }
        });
    }
    
    private void setAllEnabled(boolean isEnabled) {
    	for (JComponent component : propertiesComponents.values()) {
    		component.setEnabled(isEnabled);
		}
    	applyButton.setEnabled(isEnabled);
    }
    
	public JComponent createProperComponent(CharacterPropertyEnum characterProperty) {
		int limit = characterProperty.isLong ? 255 : 63;
		if (characterProperty.isEnum()) {
			DefaultComboBoxModel model = null;
			switch (characterProperty) {
			case WEIGHT:
				model = new DefaultComboBoxModel(WeightEnum.values());
				model.setSelectedItem(WeightEnum.NULL);
				break;
			case HEIGHT:
				model = new DefaultComboBoxModel(HeightEnum.values());
				model.setSelectedItem(HeightEnum.NULL);
				break;
			case DATE_OF_BIRTH:
				model = new DefaultComboBoxModel(DateOfBirthEnum.values());
				model.setSelectedItem(DateOfBirthEnum.NULL);
				break;
			}
			return new JComboBox(model);
		} else if (characterProperty.isSingle()) {
	        JTextField result = new JTextField();
	        result.setDocument(new LimitedAsciiDocument(limit));
	        result.setEditable(mode.isAllowFieldEdit());
	        return result;
		} else {
			if (mode.isAllowFieldEdit()) {
				StackComboBox result = new StackComboBox();
				result.getTextField().setDocument(new LimitedAsciiDocument(limit));
				return result;				
			} else {
				return new JComboBox();
			}
		}
	}
	
	@Override
	public void update(Observable o, Object arg) {
		for (CharacterPropertyEnum characterPropertyEnum : propertiesComponents.keySet()) {
			clearCharacterProperties(characterPropertyEnum);
		}
		DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(model.getCharacters().toArray());
		searchResultsComboBox.setModel(comboBoxModel);
		if (model.getCharacters().size() > 0) {
			searchResultsComboBox.setSelectedIndex(0);
		}
		foundEntriesLabel.setText("Search status: showing " + model.getCharacters().size() + " out of " + model.getSearchResultsSize());
		if (mode.equals(EntryMode.Edit) || mode.equals(EntryMode.Delete)) {
			setAllEnabled(!model.getCharacters().isEmpty());
		}
	}
	
	@Override
	public void actionPerformed(ActionEvent arg0) {
	    if (mode.equals(EntryMode.New)) {
	        try {
                model.setSelectedCharacter((FictionalCharacter)model.getSelectedCharacter().clone());
            } catch (CloneNotSupportedException e) {
                // This will never happen.
                e.printStackTrace();
            }
	    }
	    
		if (mode.equals(EntryMode.Edit) || mode.equals(EntryMode.New)) {
			fillSelectedCharacter(model.getSelectedCharacter());
		}
	}

	private void displayCharacter(FictionalCharacter character) {
		addCharacterProperties(CharacterPropertyEnum.NAME, character.getName());
		addCharacterProperties(CharacterPropertyEnum.GENDER, character.getGender());
		addCharacterProperties(CharacterPropertyEnum.HEIGHT, character.getHeight());
		addCharacterProperties(CharacterPropertyEnum.WEIGHT, character.getWeight());
		addCharacterProperties(CharacterPropertyEnum.BASED_ON, character.getBasedOn());
		addCharacterProperties(CharacterPropertyEnum.OBJECTS, character.getHasPossesed());
		addCharacterProperties(CharacterPropertyEnum.SPECIES, character.getSpecies());
		addCharacterProperties(CharacterPropertyEnum.OCCUPATION, character.getOccupation());
		addCharacterProperties(CharacterPropertyEnum.RANK, character.getRank());
		addCharacterProperties(CharacterPropertyEnum.ORGANIZATION, character.getOrganizations());
		addCharacterProperties(CharacterPropertyEnum.POWERS, character.getPowersOrAbilities());
		addCharacterProperties(CharacterPropertyEnum.UNIVERSE, character.getAppearsInTheseFictinalUniverses());
		addCharacterProperties(CharacterPropertyEnum.CREATED_BY, character.getCharacterCreatedBy());
		addCharacterProperties(CharacterPropertyEnum.ETHNICITY, character.getEthnicity());
		addCharacterProperties(CharacterPropertyEnum.EDUCATION, character.getEducation());
		addCharacterProperties(CharacterPropertyEnum.MEDICAL_CONDITION, character.getMedicalConditions());
		addCharacterProperties(CharacterPropertyEnum.PLACES_LIVED, character.getPlacesLived());
		addCharacterProperties(CharacterPropertyEnum.PLACE_OF_BIRTH, character.getPlaceOfBirth());
		addCharacterProperties(CharacterPropertyEnum.EMPLOYER, character.getEmployers());
		addCharacterProperties(CharacterPropertyEnum.PARENT, character.getParents());
		addCharacterProperties(CharacterPropertyEnum.CHILDREN, character.getChildren());
		addCharacterProperties(CharacterPropertyEnum.MARRIED_TO, character.getMarriedTo());
		addCharacterProperties(CharacterPropertyEnum.ROMANTICALLY_INVOLVED_WITH, character.getRomanticallyInvolvedWith());
		addCharacterProperties(CharacterPropertyEnum.SIBLING, character.getSiblings());
		addCharacterProperties(CharacterPropertyEnum.QUOTATION, character.getQuotations());
		Pair<DateOfBirthEnum, String> property = character.getDateOfBirth() == null || character.getDateOfBirth().isEmpty() ?
				null : character.getDateOfBirth().iterator().next();
		addCharacterProperties(CharacterPropertyEnum.DATE_OF_BIRTH, property == null ? null : property.getLeft());
		addCharacterProperties(CharacterPropertyEnum.OTHER_DATE, property == null ? null : property.getRight());
	} 
	
	private void addCharacterProperties(CharacterPropertyEnum characterProperty, Set<String> properties) {
		if (properties != null) {
			if (characterProperty.isSingle()) {
				JTextField field = getPropertyTextField(characterProperty);
				field.setText(properties.isEmpty() ? EMPTY_STRING : properties.iterator().next() );
			} else if (mode.isAllowFieldEdit()) {
				StackComboBox stackComboBox = getPropertyStackComboBox(characterProperty);
				stackComboBox.setItems(properties);
			} else {
				JComboBox comboBox = getPropertyComboBox(characterProperty);
				comboBox.setModel(new DefaultComboBoxModel(properties.toArray()));
			}
		} else {
			clearCharacterProperties(characterProperty);
		}
	}
	
	private void clearCharacterProperties(CharacterPropertyEnum characterProperty) {
		if (characterProperty.isEnum()) {
			JComboBox comboBox = getPropertyComboBox(characterProperty);
			switch (characterProperty) {
			case WEIGHT:
				comboBox.setSelectedItem(WeightEnum.NULL);
				break;
			case HEIGHT:
				comboBox.setSelectedItem(HeightEnum.NULL);
				break;
			case DATE_OF_BIRTH:
				comboBox.setSelectedItem(DateOfBirthEnum.NULL);
				break;
			}
		} else if (characterProperty.isSingle()) {
			JTextField field = getPropertyTextField(characterProperty);
			field.setText(EMPTY_STRING);
		} else if (mode.isAllowFieldEdit()) {
			StackComboBox stackComboBox = getPropertyStackComboBox(characterProperty);
			stackComboBox.clear();
		} else {
			JComboBox comboBox = getPropertyComboBox(characterProperty);
			comboBox.setModel(new DefaultComboBoxModel());
		}
	}
	
	private void addCharacterProperties(CharacterPropertyEnum characterProperty, String property) {
		JTextField field = getPropertyTextField(characterProperty);
		field.setText( property == null ? EMPTY_STRING : property );
	}
	
	private void addCharacterProperties(CharacterPropertyEnum characterProperty, HeightEnum property) {
		JComboBox comboBox = getPropertyComboBox(characterProperty);
		comboBox.setSelectedItem(property != null ? property : HeightEnum.NULL);
	}
	
	private void addCharacterProperties(CharacterPropertyEnum characterProperty, WeightEnum property) {
		JComboBox comboBox = getPropertyComboBox(characterProperty);
		comboBox.setSelectedItem(property != null ? property : WeightEnum.NULL);
	}
	
	private void addCharacterProperties(CharacterPropertyEnum characterProperty, DateOfBirthEnum property) {
		JComboBox comboBox = getPropertyComboBox(characterProperty);
		comboBox.setSelectedItem(property != null ? property : DateOfBirthEnum.NULL);
	}
	
	private void fillSelectedCharacter(FictionalCharacter character) {
		character.setName(getPropertyTextField(CharacterPropertyEnum.NAME).getText());
		character.setGender(getPropertyTextField(CharacterPropertyEnum.GENDER).getText());
		character.setHeight((HeightEnum)getPropertyComboBox(CharacterPropertyEnum.HEIGHT).getSelectedItem());
		character.setWeight((WeightEnum)getPropertyComboBox(CharacterPropertyEnum.WEIGHT).getSelectedItem());
		
		DateOfBirthEnum dateOfBirth = (DateOfBirthEnum)getPropertyComboBox(CharacterPropertyEnum.DATE_OF_BIRTH).getSelectedItem();
		String otherDate = null;
		if (dateOfBirth == DateOfBirthEnum.NULL) {
		    otherDate = getPropertyTextField(CharacterPropertyEnum.OTHER_DATE).getText();
		}
		Set<Pair<DateOfBirthEnum, String>> set = new HashSet<Pair<DateOfBirthEnum,String>>();
		set.add(new Pair<DateOfBirthEnum, String>(dateOfBirth, otherDate));
		character.setDateOfBirth(set);
	}
	
	private JTextField getPropertyTextField(CharacterPropertyEnum property) {
		return (JTextField) propertiesComponents.get(property);
	}
	
	private StackComboBox getPropertyStackComboBox(CharacterPropertyEnum property) {
		return (StackComboBox) propertiesComponents.get(property);
	}
	
	private JComboBox getPropertyComboBox(CharacterPropertyEnum property) {
		return (JComboBox) propertiesComponents.get(property);
	}
	
    public static enum EntryMode {
        New("Insert new entry", true), 
        Edit("Apply changes", true), 
        Delete("Delete entry", false);
        
        private String applyButtonText;
        private boolean allowFieldEdit;
        
        private EntryMode(String applyButtonText, boolean allowFieldsEdit) {
            this.applyButtonText = applyButtonText;
            this.allowFieldEdit = allowFieldsEdit;
        }

        public String getApplyButtonText() {
            return applyButtonText;
        }

        public boolean isAllowFieldEdit() {
            return allowFieldEdit;
        }     
    }
    
    enum CharacterPropertyEnum {
    	NAME("Name",true,false,false),    	
    	GENDER("Gender",true,false,false),
    	
    	HEIGHT("Height",true,false,true),    	
    	WEIGHT("Weight",true,false,true),
    	
    	DATE_OF_BIRTH("Date of birth",true,false,true),
    	OTHER_DATE("Other date",true,false,false),
    	
    	BASED_ON("Based on",false,false,false),
    	OBJECTS("Objects",false,false,false),
    	
    	SPECIES("Species",false,false,false),
    	OCCUPATION("Occupation",false,false,false),
    	
    	RANK("Rank",false,false,false),
    	ORGANIZATION("Organization",false,false,false),
    	
    	POWERS("Powers",false,false,false),
    	UNIVERSE("Universe",false,false,false),
    	
    	CREATED_BY("Created by",false,false,false),
    	ETHNICITY("Ethnicity",false,false,false),
    	
    	EDUCATION("Education",false,false,false),
    	MEDICAL_CONDITION("Condition",false,false,false),
    	
    	PLACES_LIVED("Places lived",false,false,false),
    	PLACE_OF_BIRTH("Place of birth",false,false,false),
    	
    	EMPLOYER("Employer",false,false,false),   	
    	PARENT("Parent",false,false,false),
    	
    	CHILDREN("Children",false,false,false), 	
    	MARRIED_TO("Married to",false,false,false),
    	
    	ROMANTICALLY_INVOLVED_WITH("Involved with",false,false,false),   	
    	SIBLING("Sibling",false,false,false),
    	
    	QUOTATION("Quotation",false,true,false);
    	
    	private final String propertyName;
		private final boolean isSingle;
		private final boolean isLong;
		private final boolean isEnum;

		CharacterPropertyEnum(String propertyName, boolean isSingle, boolean isLong, boolean isEnum) {
			this.propertyName = propertyName;
			this.isSingle = isSingle;
			this.isLong = isLong;
			this.isEnum = isEnum;		
    	}

		public String getPropertyName() {
			return propertyName;
		}

		public boolean isLong() {
			return isLong;
		}

		public boolean isSingle() {
			return isSingle;
		}

		public boolean isEnum() {
			return isEnum;
		}		
    }

}
